#ifndef GENERIK_ETRACING_H
#define GENERIK_ETRACING_H
// :mode=c++:

/** \file etracing.h

This header defines a couple of macros and a framework for
printf-style debugging. If no tracing type is defined, the default
is to output nothing: all the macros evaluate as empty.
The two types of tracing are GENERIK_TRACING and GENERIK_LOGGING

\def GENERIK_TRACING
Enables deep tracing. In every scope where a
GENERIK_TRACER is placed, a tracing statement will be output.
To output general messages, use GENERIK_TRACE. Logging and
error-reporting is available through GENERIK_LOG and
GENERIK_ERROR, respectively.

\def GENERIK_LOGGING
If GENERIK_TRACING is not defined, but GENERIK_LOGGING is,
then only logging and error messages will be output.

*/

#ifdef GENERIK_TRACING

#include <iostream>
#include <fstream>
#include <sstream>
#include <cxxabi.h>
#include <unistd.h>
#include <sys/resource.h>

/*###########################
# Generik tracing macros ####
###########################*/

/** \def GENERIK_TRACER_GLOBAL
Instantiates the global tracer, which outputs some prelimenary debug info,
and also outputs a message when it leaves scope. This macro should be called
by the main source file, before any other structure is declared.
It should also be only declared once.
*/
#define GENERIK_TRACER_GLOBAL \
	std::ostream* generik::tracing::tracer::_stream = &std::clog; \
	generik::tracing::tracer generik_tracing_global_tracer("process"); \

#define GENERIK_TRACER_GLOBAL_TO_FILE(FILENAME) \
	std::ostream* generik::tracing::tracer::_stream; \
	generik::tracing::tracer generik_tracing_global_tracer("process", #FILENAME".dump");

/** \def GENERIK_TRACER
Instantiates a local tracer, which automatically logs the name of the function
where it resides. Upon destruction, the tracer logs an exit message.
*/
#define GENERIK_TRACER \
	generik::tracing::tracer generik_tracing_tracer(__PRETTY_FUNCTION__)

/** \def REM_GENERIK_TRACER
Helper object. Use this to silence the existing tracers in the code, without
having to remove them.
*/
#define REM_GENERIK_TRACER \
	generik::tracing::tracer generik_tracing_tracer

/** \def GENERIK_TRACE(message)
Outputs the given message. The message may be in stream-format, ie:
\code GENERIK_TRACE("hello "<<world); \endcode
*/
#define GENERIK_TRACE(message) \
	{ \
		std::stringstream buf; \
		buf<<message; \
		generik_tracing_tracer.trace(buf.str()); \
	}

/** \def GENERIK_LOG(message)
Outputs the given log message. The format of the message is the same
as for GENERIK_TRACE.
This message will also be output if GENERIK_LOGGING is defined.
*/
#define GENERIK_LOG(message) \
	{ \
		std::stringstream buf; \
		buf<<message; \
		generik_tracing_tracer.log(buf.str()); \
	}

/** \def GENERIK_ERROR(message)
Logs the given message as an error.The format of the message is the same
as for GENERIK_TRACE.
This message will also be output if GENERIK_LOGGING is defined.
*/
#define GENERIK_ERROR(message) \
	{ \
		std::stringstream buf; \
		buf<<message; \
		generik_tracing_tracer.error(buf.str()); \
		std::cerr<<buf.str()<<std::endl; \
	}

/** \def GENERIK_ASSERT(evaluation)
Logs the evaluation's result. If the evaluation fails, this will be output
as an error-type message (see GENERIK_ERROR).
If GENERIK_LOGGING is defined, the message will only be output if the
evaluation fails.
*/
#define GENERIK_ASSERT(evaluation) \
	{ \
		generik_tracing_tracer.assert_(#evaluation, evaluation); \
	}

/** \def GENERIK_TRACE_VALUE(instance)
Shortcut which traces the value of the instance and its name
*/
#define GENERIK_TRACE_VALUE(instance) \
	{ \
		GENERIK_TRACE(#instance << " == [" << instance << "]"); \
	}

#ifdef GENERIK_TRACE_VMEM
// since we use an fstream to read from /proc if GENERIK_TRACE_VMEM
// is defined, we need to include this:
#include <fstream>
#endif

namespace generik
{

	namespace tracing
	{

		/** Tracer.
		Scoped reporting object. When this is created, it makes a "start" log-entry,
		and when destroyed makes a "stop" log-entry.
		If it is the first declaration in a function body, it will nicely delimit the
		borders of the function in the log output, which is the whole idea.
		\warning Do not use this directly in the code. Please use the Macros.
		*/
		struct tracer
		{
			static std::ostream* _stream;
			bool _active;
			std::string _function;

			std::string itoa(int value_in)
			{
				std::stringstream string;
				string<<value_in<<" ";
				return string.str();
			}
			std::string at()
			{
				return itoa(clock());
			}
			int pid()
			{
				return getpid();
			}
			pthread_t tid()
			{
				return pthread_self();
			}
			std::string mem()
			{
#ifdef GENERIK_TRACE_VMEM
				// read the virtual memory as reported by /proc
				std::stringstream s;
				s<<"/proc/"<<getpid()<<"/stat";
				std::ifstream f(s.str().c_str());
				std::string vsize;
				// it is the 23rd entry in the output of /proc/*/stat
				for (int i = 0; i < 23; ++i) f>>vsize;
				s.str("");
				s<<"["<<vsize<<"]";
				return s.str();
#else
				return "*";
#endif
			}

#ifndef GENERIK_TRACE_QUIET					
			void msg(char pre, const std::string& message_in)
			{
				if (_active)
				{
					(*_stream)<<pre<<" "<<pid()<<" "<<(uint)tid()<<" "<<mem()<<" "<<message_in<<std::endl;
				}
			}
#else
			void msg(char pre, const std::string& message_in)
			{
			}		
#endif

			tracer()
			: _active(false)
			{}
			tracer(const std::string& function_in)
			: _active(true), _function(function_in)
			{
				msg('+', _function);
			}
			~tracer()
			{
				msg('-', _function);
			}
			tracer(const std::string& function_in, const std::string& filename_in)
			: _active(true), _function(function_in)
			{
				_stream = new std::ofstream(filename_in.c_str());
			}

			void trace(const std::string& message_in)
			{
				msg(':', "\t" + message_in);
			}
			void log(const std::string& message_in)
			{
				msg('#', "\t"+ message_in);
			}
			void error(const std::string& message_in)
			{
				msg('E', "\t" + message_in);
			}
			void assert_(const std::string& evalstr_in, bool success)
			{
				if (success)
				{
					msg('%', evalstr_in + " is TRUE");
				}
				else
				{
					msg('%', evalstr_in + " is FALSE");
				}
			}

		};

	}

}

//std::ostream* generik::tracing::tracer::_stream = &std::clog;

#else // ifndef GENERIK_TRACING 

/*####################
# En/Disable Logging #
####################*/

#ifdef GENERIK_TRACE_LOG

#include <assert.h>
#include <iostream>

#define GENERIK_ERROR(message) \
std::cerr<<"ERROR: "<<message<<std::endl;
#define GENERIK_LOG(message) \
std::clog<<message<<std::endl;
#define GENERIK_ASSERT(evaluation) \
assert(evaluation);

#else // ifndef GENERIK_TRACE_LOG

#define GENERIK_ERROR(message)
#define GENERIK_LOG(message)
#define GENERIK_ASSERT(evaluation)

#endif // GENERIK_TRACER_LOG

/*#################
# Disable tracing #
#################*/

#define GENERIK_TRACER_GLOBAL
#define GENERIK_TRACER_GLOBAL_TO_FILE(FILENAME)

#define GENERIK_TRACER
#define REM_GENERIK_TRACER
#define GENERIK_TRACE(message)
#define GENERIK_TRACE_VALUE(instance)

#endif // GENERIK_TRACING

#endif
// GENERIK_TRACING_H

